﻿using System;
using System.Xml;
using Notoric.Model.Internal;
using Notoric.Model.Packaging;

namespace Notoric.Model.Elements.CWNotation
{
    /// <summary>
    /// A common base class for attachments that present attachment symbols from composition data.
    /// </summary>
    public abstract class PresentedAttachment : Attachment
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="Attachment"/> instance.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="XmlPackagePart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="Staff"/>
        /// </para>
        /// <para>
        /// The model element that directly owns this element.
        /// </para>
        /// </param>
        /// <param name="presentedAttachment">
        /// <para>
        /// Type: <see cref="AttachmentElement"/>
        /// </para>
        /// <para>
        /// The presented attachment element.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="packagePart"/> or <paramref name="owner"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// The <paramref name="presentedAttachment"/> does not belong to the same part as the <paramref name="owner"/>.
        /// </exception>
        internal PresentedAttachment(XmlPackagePart packagePart, Staff owner, AttachmentElement presentedAttachment)
            : base(packagePart, owner)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");
            if (presentedAttachment == null)
                throw new ArgumentNullException("presentedAttachment");
            if (presentedAttachment.FindOwner<PartElement>().Descriptor != owner.Descriptor.FindOwner<PartDescriptorElement>())
                throw new ArgumentException("The presented attachment has to belong to the same part as the owner staff.", "presentedAttachment");

            this.presentedAttachment = presentedAttachment;
            presentedAttachmentAttribute.Value = presentedAttachment.ID.ToString();
            typeAttribute.Value = GetType().FullName;

            position.SetOwner(positionElement);
        }

        /// <summary>
        /// Creates a new <see cref="Attachment"/> instance.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="XmlPackagePart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="Staff"/>
        /// </para>
        /// <para>
        /// The model element that directly owns this element.
        /// </para>
        /// </param>
        /// <param name="element">
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The XML element that holds the data of this object.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Any of the arguments is <see langword="null"/>.
        /// </exception>
        internal PresentedAttachment(XmlPackagePart packagePart, Staff owner, XmlElement element)
            : base(packagePart, owner, element)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");

            presentedAttachmentAttribute = element.Attributes[PackageVocabulary.IDRefAttribute];
            typeAttribute = element.Attributes[PackageVocabulary.TypeAttribute];

            positionElement = element[PackageVocabulary.PositionElementName, PackageVocabulary.PackageNamespaceUri];
            position = new Point(positionElement.FirstChild as XmlElement);
        }

        #endregion

        #region ModelElement Members

        /// <inheritdoc/>
        protected override XmlElement CreateAttachmentElement(XmlDocument document)
        {
            var element = CreateSymbolElement(document);

            presentedAttachmentAttribute = document.CreateAttribute(PackageVocabulary.IDRefAttribute);
            typeAttribute = document.CreateAttribute(PackageVocabulary.TypeAttribute);

            positionElement = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.PositionElementName,
                PackageVocabulary.PackageNamespaceUri
                );

            element.Attributes.Append(presentedAttachmentAttribute);
            element.Attributes.Append(typeAttribute);

            element.AppendChild(positionElement);

            return element;
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// When overriden in a derived class, this method creates the XML element
        /// that will holds the data of this attachment.
        /// </summary>
        /// <remarks>
        /// The attributes for the inherited members as well as the type information
        /// will be added by the base class.
        /// </remarks>
        /// <param name="document">
        /// <para>
        /// Type: <see cref="XmlDocument"/>
        /// </para>
        /// <para>
        /// The document that will own the created element.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The created XML element.
        /// </para>
        /// </returns>
        protected abstract XmlElement CreateSymbolElement(XmlDocument document);

        #endregion

        #region Public Methods

        /// <summary>
        /// Creates the representation for a given attachment element.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="XmlPackagePart"/>
        /// </para>
        /// <para>
        /// The package part that will own the created symbol.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="Staff"/>
        /// </para>
        /// <para>
        /// The staff that will own the created symbol.
        /// </para>
        /// </param>
        /// <param name="presentedElement">
        /// <para>
        /// Type: <see cref="AttachmentElement"/>
        /// </para>
        /// <para>
        /// The presented element.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="PresentedAttachment"/>
        /// </para>
        /// <para>
        /// The Common Western Music Notation representation of the <paramref name="presentedElement"/>.
        /// </para>
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// The presented element is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// The <paramref name="presentedElement"/> has an unknown type.
        /// </exception>
        public static PresentedAttachment CreateRepresentation(XmlPackagePart packagePart, Staff owner, AttachmentElement presentedElement)
        {
            if (presentedElement is DynamicMarkElement)
                return new DynamicMark(packagePart, owner, presentedElement as DynamicMarkElement);
            else if (presentedElement is DynamicsProgressElement)
                return new DynamicsProgressMark(packagePart, owner, presentedElement as DynamicsProgressElement);

            throw new ArgumentException("Unknown attachment type!", "presentedElement");
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the position of this attachment.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Point"/>
        /// </para>
        /// <para>
        /// The position of the attachment.<br/>
        /// The values are in centimeters and relative to the top-left
        /// corner of the owner system.
        /// </para>
        /// </value>
        public Point Position
        {
            get { return new Point(position); }
            set
            {
                position.SetOwner(null);
                position = value;
                position.SetOwner(positionElement);

                RaisePropertyChanged("Position");
            }
        }

        /// <summary>
        /// Gets the presented attachment element.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="AttachmentElement"/>
        /// </para>
        /// <para>
        /// The presented attachment element.
        /// </para>
        /// </value>
        public AttachmentElement PresentedElement
        {
            get 
            {
                if (presentedAttachment == null)
                    presentedAttachment = PackagePart.Package.IDMap.FindObject<AttachmentElement>(new Guid(presentedAttachmentAttribute.Value));

                return presentedAttachment;
            }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the value of the <see cref="Position"/> property.
        /// </summary>
        private Point position;
        /// <summary>
        /// Holds the value of the <see cref="Position"/> property.
        /// </summary>
        private XmlElement positionElement;
        /// <summary>
        /// Holds the presented attachment element.
        /// </summary>
        private AttachmentElement presentedAttachment;
        /// <summary>
        /// Holds the identifier of the presented attachment element.
        /// </summary>
        private XmlAttribute presentedAttachmentAttribute;
        /// <summary>
        /// Holds the type of the attachment's class.
        /// </summary>
        private XmlAttribute typeAttribute;

        #endregion
    }
}
